using System;

namespace Glue.Internals.Verification
{
    internal class AssertMapsPropertyCorrectly
    {
        private readonly IRelation relation;
        private readonly IObjectMember fromPropertyInfo;
        private readonly IObjectMember toPropertyInfo;
        private readonly object receiver;
        private readonly AssertMapsCorrectlyParameters parameters;
        private readonly ConvertibleRandomValues randomizers;
        public String ErrorMessage { get; private set; }

        public AssertMapsPropertyCorrectly(IRelation relation, IObjectMember fromPropertyInfo, IObjectMember toPropertyInfo, 
            object receiver, AssertMapsCorrectlyParameters parameters)
        {
            ErrorMessage = "";
            this.relation = relation;
            this.fromPropertyInfo = fromPropertyInfo;
            this.toPropertyInfo = toPropertyInfo;
            this.receiver = receiver;
            this.parameters = parameters;
            randomizers = parameters.TheRandomizers;
        }

        public bool Map()
        {
            if (fromPropertyInfo is ICollectionMember)
                return VerifyCollectionType();
            if (fromPropertyInfo is INestedMember)
                return VerifyNestedType();

            return Verify();
        }

        private bool VerifyCollectionType()
        {
            if (!VerifyCollectionSets())
                return false;

            //var collectionMapping = (CollectionMapping) relation.GetConverter();
            //var nestedConverter = collectionMapping.Converter as IHoldNestedRelation;
            //if (nestedConverter != null)
            //{
            //    var nestedReceiver = collectionMapping.GetCreator((ICollectionMember)fromPropertyInfo)(null);
            //    return VerifyNestedType(nestedReceiver,nestedConverter); 
            //}
                
            return true;
        }

        private bool VerifyCollectionSets()
        {
            var glueCollection = new EmptyGlueCollection(fromPropertyInfo);
            var result = relation.GetConverter().Map(glueCollection, null);

            if (!TrySetValue(result))
                return false;

            if (toPropertyInfo.InvokeGet(receiver) == null)
            {
                ErrorMessage = toPropertyInfo.Name + " (Failed to set value)";
                return false;
            }
            return true;
        }

        private bool Verify()
        {
            object newValue = GenerateValue();
            if (newValue == null)
                return false;

            if (!TrySetValue(newValue))
                return false;

            if (!IsValueSet(newValue))
            {
                ErrorMessage = toPropertyInfo.Name + " (Failed to set value)";
                return false;
            }
            return true;
        }

        private object GenerateValue()
        {
            var sendersPropertyType = fromPropertyInfo.MemberType;
            var oldValue = toPropertyInfo.InvokeGet(receiver);
            var generator = new RandomValueGenerator(oldValue,
                toPropertyInfo.MemberType, sendersPropertyType, relation.GetConverter(), randomizers);
            var generatedValue = generator.GenerateValue();
            if (generatedValue == null)
                ErrorMessage = toPropertyInfo.Name + " (Can't get a random value for type '" + sendersPropertyType.Name + "'. Always gets '" + oldValue + "'. Check Randomizer and Converter)";
            
            return generatedValue;
        }

        private bool TrySetValue(object newValue)
        {
            try
            {
                toPropertyInfo.InvokeSet(receiver, newValue);
            }
            catch (Exception ex)
            {
                LogException(toPropertyInfo.Name, ex);
                return false;
            }

            return true;
        }

        private bool IsValueSet(object newValue)
        {
            return toPropertyInfo.InvokeGet(receiver).Equals(newValue);
        }

        private bool VerifyNestedType()
        {
            var nestedReceiver = toPropertyInfo.InvokeGet(receiver);
            if (nestedReceiver == null)
            {
                ErrorMessage = toPropertyInfo.Name + " (Seems to be a nested relation, but the value is Null. You need to set an instance on this member to be able to run this test)";
                return false;
            }

            return VerifyNestedType(nestedReceiver, (IHoldNestedRelation)relation.GetConverter());
        }

        private bool VerifyNestedType(object nestedReceiver, IHoldNestedRelation converter)
        {
            try
            {
                new AssertMapsCorrectly(parameters.GetNestedRelations(converter),
                                        nestedReceiver, parameters).Assert();
                return true;
            }
            catch (GlueException ex)
            {
                ErrorMessage = toPropertyInfo.Name + "(Nested): (" + ex.Message + ")";
                return false;
            }
        }

        private void LogException(String propertyName, Exception ex)
        {
            var exceptionName = ex.GetType().Name + ": " + ex.Message;

            if (ex.InnerException != null)
                exceptionName = ex.InnerException.GetType().Name + ": " + ex.InnerException.Message;

            ErrorMessage = propertyName + " (Threw '" + exceptionName + "')";
        }
    }

    internal class EmptyGlueCollection:IGlueCollection
    {
        private readonly IObjectMember createdBy;

        public EmptyGlueCollection(IObjectMember createdBy)
        {
            this.createdBy = createdBy as ICollectionMember;
        }

        public bool MoveNext()
        {
            return false;
        }

        public object Current
        {
            get { throw new NotImplementedException(); }
        }

        public int Count
        {
            get { return 0; }
        }

        public void Add(object value)
        {
            throw new NotImplementedException();
        }

        public bool CreatedBy(ICollectionMember collectionMember)
        {
            return createdBy == collectionMember;
        }
    }
}